// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/files/file_proxy.h"

#include <utility>

#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/files/file.h"
#include "base/files/file_util.h"
#include "base/location.h"
#include "base/macros.h"
#include "base/task_runner.h"
#include "base/task_runner_util.h"

namespace {

void FileDeleter(base::File file)
{
}

} // namespace

namespace base {

class FileHelper {
public:
    FileHelper(FileProxy* proxy, File file)
        : file_(std::move(file))
        , error_(File::FILE_ERROR_FAILED)
        , task_runner_(proxy->task_runner())
        , proxy_(AsWeakPtr(proxy))
    {
    }

    void PassFile()
    {
        if (proxy_)
            proxy_->SetFile(std::move(file_));
        else if (file_.IsValid())
            task_runner_->PostTask(FROM_HERE, Bind(&FileDeleter, Passed(&file_)));
    }

protected:
    File file_;
    File::Error error_;

private:
    scoped_refptr<TaskRunner> task_runner_;
    WeakPtr<FileProxy> proxy_;
    DISALLOW_COPY_AND_ASSIGN(FileHelper);
};

namespace {

    class GenericFileHelper : public FileHelper {
    public:
        GenericFileHelper(FileProxy* proxy, File file)
            : FileHelper(proxy, std::move(file))
        {
        }

        void Close()
        {
            file_.Close();
            error_ = File::FILE_OK;
        }

        void SetTimes(Time last_access_time, Time last_modified_time)
        {
            bool rv = file_.SetTimes(last_access_time, last_modified_time);
            error_ = rv ? File::FILE_OK : File::FILE_ERROR_FAILED;
        }

        void SetLength(int64_t length)
        {
            if (file_.SetLength(length))
                error_ = File::FILE_OK;
        }

        void Flush()
        {
            if (file_.Flush())
                error_ = File::FILE_OK;
        }

        void Reply(const FileProxy::StatusCallback& callback)
        {
            PassFile();
            if (!callback.is_null())
                callback.Run(error_);
        }

    private:
        DISALLOW_COPY_AND_ASSIGN(GenericFileHelper);
    };

    class CreateOrOpenHelper : public FileHelper {
    public:
        CreateOrOpenHelper(FileProxy* proxy, File file)
            : FileHelper(proxy, std::move(file))
        {
        }

        void RunWork(const FilePath& file_path, int file_flags)
        {
            file_.Initialize(file_path, file_flags);
            error_ = file_.IsValid() ? File::FILE_OK : file_.error_details();
        }

        void Reply(const FileProxy::StatusCallback& callback)
        {
            DCHECK(!callback.is_null());
            PassFile();
            callback.Run(error_);
        }

    private:
        DISALLOW_COPY_AND_ASSIGN(CreateOrOpenHelper);
    };

    class CreateTemporaryHelper : public FileHelper {
    public:
        CreateTemporaryHelper(FileProxy* proxy, File file)
            : FileHelper(proxy, std::move(file))
        {
        }

        void RunWork(uint32_t additional_file_flags)
        {
            // TODO(darin): file_util should have a variant of CreateTemporaryFile
            // that returns a FilePath and a File.
            if (!CreateTemporaryFile(&file_path_)) {
                // TODO(davidben): base::CreateTemporaryFile should preserve the error
                // code.
                error_ = File::FILE_ERROR_FAILED;
                return;
            }

            uint32_t file_flags = File::FLAG_WRITE | File::FLAG_TEMPORARY | File::FLAG_CREATE_ALWAYS | additional_file_flags;

            file_.Initialize(file_path_, file_flags);
            if (file_.IsValid()) {
                error_ = File::FILE_OK;
            } else {
                error_ = file_.error_details();
                DeleteFile(file_path_, false);
                file_path_.clear();
            }
        }

        void Reply(const FileProxy::CreateTemporaryCallback& callback)
        {
            DCHECK(!callback.is_null());
            PassFile();
            callback.Run(error_, file_path_);
        }

    private:
        FilePath file_path_;
        DISALLOW_COPY_AND_ASSIGN(CreateTemporaryHelper);
    };

    class GetInfoHelper : public FileHelper {
    public:
        GetInfoHelper(FileProxy* proxy, File file)
            : FileHelper(proxy, std::move(file))
        {
        }

        void RunWork()
        {
            if (file_.GetInfo(&file_info_))
                error_ = File::FILE_OK;
        }

        void Reply(const FileProxy::GetFileInfoCallback& callback)
        {
            PassFile();
            DCHECK(!callback.is_null());
            callback.Run(error_, file_info_);
        }

    private:
        File::Info file_info_;
        DISALLOW_COPY_AND_ASSIGN(GetInfoHelper);
    };

    class ReadHelper : public FileHelper {
    public:
        ReadHelper(FileProxy* proxy, File file, int bytes_to_read)
            : FileHelper(proxy, std::move(file))
            , buffer_(new char[bytes_to_read])
            , bytes_to_read_(bytes_to_read)
            , bytes_read_(0)
        {
        }

        void RunWork(int64_t offset)
        {
            bytes_read_ = file_.Read(offset, buffer_.get(), bytes_to_read_);
            error_ = (bytes_read_ < 0) ? File::FILE_ERROR_FAILED : File::FILE_OK;
        }

        void Reply(const FileProxy::ReadCallback& callback)
        {
            PassFile();
            DCHECK(!callback.is_null());
            callback.Run(error_, buffer_.get(), bytes_read_);
        }

    private:
        std::unique_ptr<char[]> buffer_;
        int bytes_to_read_;
        int bytes_read_;
        DISALLOW_COPY_AND_ASSIGN(ReadHelper);
    };

    class WriteHelper : public FileHelper {
    public:
        WriteHelper(FileProxy* proxy,
            File file,
            const char* buffer, int bytes_to_write)
            : FileHelper(proxy, std::move(file))
            , buffer_(new char[bytes_to_write])
            , bytes_to_write_(bytes_to_write)
            , bytes_written_(0)
        {
            memcpy(buffer_.get(), buffer, bytes_to_write);
        }

        void RunWork(int64_t offset)
        {
            bytes_written_ = file_.Write(offset, buffer_.get(), bytes_to_write_);
            error_ = (bytes_written_ < 0) ? File::FILE_ERROR_FAILED : File::FILE_OK;
        }

        void Reply(const FileProxy::WriteCallback& callback)
        {
            PassFile();
            if (!callback.is_null())
                callback.Run(error_, bytes_written_);
        }

    private:
        std::unique_ptr<char[]> buffer_;
        int bytes_to_write_;
        int bytes_written_;
        DISALLOW_COPY_AND_ASSIGN(WriteHelper);
    };

} // namespace

FileProxy::FileProxy(TaskRunner* task_runner)
    : task_runner_(task_runner)
{
}

FileProxy::~FileProxy()
{
    if (file_.IsValid())
        task_runner_->PostTask(FROM_HERE, Bind(&FileDeleter, Passed(&file_)));
}

bool FileProxy::CreateOrOpen(const FilePath& file_path,
    uint32_t file_flags,
    const StatusCallback& callback)
{
    DCHECK(!file_.IsValid());
    CreateOrOpenHelper* helper = new CreateOrOpenHelper(this, File());
    return task_runner_->PostTaskAndReply(
        FROM_HERE,
        Bind(&CreateOrOpenHelper::RunWork, Unretained(helper), file_path,
            file_flags),
        Bind(&CreateOrOpenHelper::Reply, Owned(helper), callback));
}

bool FileProxy::CreateTemporary(uint32_t additional_file_flags,
    const CreateTemporaryCallback& callback)
{
    DCHECK(!file_.IsValid());
    CreateTemporaryHelper* helper = new CreateTemporaryHelper(this, File());
    return task_runner_->PostTaskAndReply(
        FROM_HERE,
        Bind(&CreateTemporaryHelper::RunWork, Unretained(helper),
            additional_file_flags),
        Bind(&CreateTemporaryHelper::Reply, Owned(helper), callback));
}

bool FileProxy::IsValid() const
{
    return file_.IsValid();
}

void FileProxy::SetFile(File file)
{
    DCHECK(!file_.IsValid());
    file_ = std::move(file);
}

File FileProxy::TakeFile()
{
    return std::move(file_);
}

PlatformFile FileProxy::GetPlatformFile() const
{
    return file_.GetPlatformFile();
}

bool FileProxy::Close(const StatusCallback& callback)
{
    DCHECK(file_.IsValid());
    GenericFileHelper* helper = new GenericFileHelper(this, std::move(file_));
    return task_runner_->PostTaskAndReply(
        FROM_HERE,
        Bind(&GenericFileHelper::Close, Unretained(helper)),
        Bind(&GenericFileHelper::Reply, Owned(helper), callback));
}

bool FileProxy::GetInfo(const GetFileInfoCallback& callback)
{
    DCHECK(file_.IsValid());
    GetInfoHelper* helper = new GetInfoHelper(this, std::move(file_));
    return task_runner_->PostTaskAndReply(
        FROM_HERE,
        Bind(&GetInfoHelper::RunWork, Unretained(helper)),
        Bind(&GetInfoHelper::Reply, Owned(helper), callback));
}

bool FileProxy::Read(int64_t offset,
    int bytes_to_read,
    const ReadCallback& callback)
{
    DCHECK(file_.IsValid());
    if (bytes_to_read < 0)
        return false;

    ReadHelper* helper = new ReadHelper(this, std::move(file_), bytes_to_read);
    return task_runner_->PostTaskAndReply(
        FROM_HERE,
        Bind(&ReadHelper::RunWork, Unretained(helper), offset),
        Bind(&ReadHelper::Reply, Owned(helper), callback));
}

bool FileProxy::Write(int64_t offset,
    const char* buffer,
    int bytes_to_write,
    const WriteCallback& callback)
{
    DCHECK(file_.IsValid());
    if (bytes_to_write <= 0 || buffer == NULL)
        return false;

    WriteHelper* helper = new WriteHelper(this, std::move(file_), buffer, bytes_to_write);
    return task_runner_->PostTaskAndReply(
        FROM_HERE,
        Bind(&WriteHelper::RunWork, Unretained(helper), offset),
        Bind(&WriteHelper::Reply, Owned(helper), callback));
}

bool FileProxy::SetTimes(Time last_access_time,
    Time last_modified_time,
    const StatusCallback& callback)
{
    DCHECK(file_.IsValid());
    GenericFileHelper* helper = new GenericFileHelper(this, std::move(file_));
    return task_runner_->PostTaskAndReply(
        FROM_HERE,
        Bind(&GenericFileHelper::SetTimes, Unretained(helper), last_access_time,
            last_modified_time),
        Bind(&GenericFileHelper::Reply, Owned(helper), callback));
}

bool FileProxy::SetLength(int64_t length, const StatusCallback& callback)
{
    DCHECK(file_.IsValid());
    GenericFileHelper* helper = new GenericFileHelper(this, std::move(file_));
    return task_runner_->PostTaskAndReply(
        FROM_HERE,
        Bind(&GenericFileHelper::SetLength, Unretained(helper), length),
        Bind(&GenericFileHelper::Reply, Owned(helper), callback));
}

bool FileProxy::Flush(const StatusCallback& callback)
{
    DCHECK(file_.IsValid());
    GenericFileHelper* helper = new GenericFileHelper(this, std::move(file_));
    return task_runner_->PostTaskAndReply(
        FROM_HERE,
        Bind(&GenericFileHelper::Flush, Unretained(helper)),
        Bind(&GenericFileHelper::Reply, Owned(helper), callback));
}

} // namespace base
